﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Diagnostics.Contracts;

namespace SimpleIR
{
	/// <summary>
	/// Representation of a method body.
	/// </summary>
	public sealed class IRBodyDef
	{
		private readonly IRSourceLocation m_deflocation;
		private readonly IRInvokeIdentity m_invokeID;
		private readonly IRType m_returnType;
		private readonly List<IRVarName> m_params;
		
		private readonly MethodQualifiers m_mqual;
		private readonly IRBody m_body;

		private readonly String m_builtinSemanticName;
		
		private readonly HashSet<IRStaticField> m_usedmodStaticFields;
		
		[ContractInvariantMethod]
		private void ObjectInvariant()
		{
			Contract.Invariant(this.m_deflocation != null);
			Contract.Invariant(this.m_invokeID != null);
			Contract.Invariant(this.m_returnType != null);
			Contract.Invariant(this.m_params != null);
			Contract.Invariant(((this.m_mqual & (MethodQualifiers.BuiltinTag | MethodQualifiers.AbstractTag)) != 0) | (this.m_body != null));
			Contract.Invariant(((this.m_mqual & MethodQualifiers.BuiltinTag) == 0) | (!String.IsNullOrEmpty(this.m_builtinSemanticName)));
			Contract.Invariant(this.m_usedmodStaticFields != null);
		}

		/// <summary>
		/// For allocating method body definitions of methods defined by explicit implementations.
		/// </summary>
		public IRBodyDef(IRInvokeIdentity invID, IRType rtt, List<IRVarName> paramvars, MethodQualifiers qual, IRBody bb, IRSourceLocation defloc)
		{
			Contract.Requires(defloc != null);
			Contract.Requires(invID != null);
			Contract.Requires(rtt != null);
			Contract.Requires(paramvars != null);
			Contract.Requires(((qual & MethodQualifiers.BuiltinTag) == 0), "Shouldn't be a builtin!!!");

			this.m_deflocation = defloc;
			this.m_invokeID = invID;
			this.m_returnType = rtt;
			this.m_params = paramvars;
			this.m_mqual = qual;
			this.m_body = bb;

			this.m_builtinSemanticName = null;
			
			this.m_usedmodStaticFields = new HashSet<IRStaticField>();
		}

		/// <summary>
		/// For allocating method body definitions of methods defined by builtin implementations.
		/// </summary>
		public IRBodyDef(IRInvokeIdentity invID, IRType rtt, List<IRVarName> paramvars, MethodQualifiers mqual, String biopname, List<IRStaticField> accessedstatics, IRSourceLocation defloc)
		{
			Contract.Requires(defloc != null);
			Contract.Requires(invID != null);
			Contract.Requires(rtt != null);
			Contract.Requires(paramvars != null);
			Contract.Requires(biopname != null);
			Contract.Requires(accessedstatics != null);
			Contract.Requires(((mqual & MethodQualifiers.BuiltinTag) != 0), "Should be a builtin!!!");

			this.m_deflocation = defloc;
			this.m_invokeID = invID;
			this.m_returnType = rtt;
			this.m_params = paramvars;
			
			this.m_mqual = mqual;
			this.m_body = null;

			this.m_builtinSemanticName = biopname;
			
			this.m_usedmodStaticFields = new HashSet<IRStaticField>(accessedstatics);
		}

		public override String ToString()
		{
			StringBuilder sb = new StringBuilder(this.m_invokeID.ToString() + "\n");
			bool first;

			if(this.IsAbstract)
				sb.Append("Abstract\n");

			sb.Append(this.m_mqual.ToString() + "\n");

			if(this.m_usedmodStaticFields.Count != 0)
			{
				sb.Append("StaticAccessed: ");
				first = true;
				foreach(IRStaticField rsf in this.m_usedmodStaticFields)
				{
					if(!first)
						sb.Append(", ");
					first = false;
					sb.Append(rsf.ToString());
				}
				sb.Append("\n");
			}

			if(this.IsBuiltin)
				sb.Append("Builtin: " + this.m_builtinSemanticName);
			else
				sb.Append(this.m_body.ToString());

			return sb.ToString();
		}

		internal void ComputeCallGraph(CallGraph cg, Dictionary<IRLambdaType, HashSet<IRInvokeIdentity>> delegateinvmap, IRProgram p)
		{
			if(this.m_body != null)
				this.m_body.BuildCallGraph(this.m_invokeID, cg, delegateinvmap, p);
		}

		///////////////////////////
		//Accessors

		/// <summary>
		/// Get the invoke identity of the associated body.
		/// </summary>
		public IRInvokeIdentity InvokeID
		{ get { return this.m_invokeID; } }

		/// <summary>
		/// Return the qualifier for this method
		/// </summary>
		public MethodQualifiers Qualifier
		{ get { return this.m_mqual; } }

		/// <summary>
		/// Get the return type of the associated body.
		/// </summary>
		public IRType ReturnType
		{ get { return this.m_returnType; } }

		/// <summary>
		/// Return a list of the formal variable names for this method.
		/// </summary>
		public List<IRVarName> Parameters
		{ get { return this.m_params; } }

		public bool IsBuiltin
		{ get { return (this.m_mqual & MethodQualifiers.BuiltinTag) != 0; } }
		
		public bool IsAbstract
		{ get { return (this.m_mqual & MethodQualifiers.AbstractTag) != 0; } }

		/// <summary>
		/// Get the body of the associated method (only valid for nonbuiltin methods).
		/// </summary>
		public IRBody Body 
		{ 
			get 
			{
				Contract.Requires((!this.IsBuiltin) & (!this.IsAbstract));
				Contract.Ensures(Contract.Result<IRBody>() != null);

				return this.m_body; 
			} 
		}

		/// <summary>
		/// Get the name of the builtin semantics to use for the associated method (only valid for builtin methods).
		/// </summary>
		public String BuiltinSemanticName 
		{ 
			get 
			{
				Contract.Requires(this.IsBuiltin);
				Contract.Ensures(!String.IsNullOrEmpty(Contract.Result<String>()));

				return this.m_builtinSemanticName; 
			} 
		}

		/// <summary>
		/// Get the set of static fields that may be used/modified in the call and any sub calls (only valid for methods that you know are fully resolved).
		/// </summary>
		public HashSet<IRStaticField> UsedModStaticFields
		{ get { return this.m_usedmodStaticFields; } }
	}
}
